﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using swe.Core;

namespace BibtexParser
{
	public static class BibtexParser
	{

		public static IEnumerable<Bib> ExtractBibs(string content)
		{
			var bibs = new List<Bib>();
			var sm = StateMachine<int, int>.Create("Space")
				.State(
					   "Space",
					   (s, i) =>
					   {
						   if (i >= content.Length)
							   return StateMachine.Break;
						   if (content[i] == '@')
							   return StateMachine.NamedState("BibtexItem");
						   s.UpdateObject(i + 1);
						   return StateMachine.Loop;
					   })
				.State("BibtexItem",
					   (s, i) =>
					   {
						   var newBib = new Bib();
						   var nextI = content.IndexOf("{", i);
						   if (nextI < 0)
							   throw new ArgumentOutOfRangeException(string.Format("Incorrect format at position {0}", i));
						   newBib.Type = content.Substring(i, nextI - i);
						   bibs.Add(newBib);
						   s.UpdateObject(nextI);
						   return StateMachine.NamedState("Content");
					   })
				.State("Content",
					   (s, i) =>
					   {
						   s.UpdateObject(ProcessContent(content, i+1, bibs.Last()));
						   return StateMachine.NamedState("Space");
					   })
				.BindTo(0);
			while (!sm.Process()) { }
			return bibs;
		}

		private static int ProcessContent(string content, int initialI, Bib bib)
		{
			var propName = "";
			var bracketDepth = 0;
			var returnI = initialI;
			var propValBuilder = new StringBuilder();
			var sm = StateMachine<int, int>.Create("Id")
				.State("Id",
					   (s, i) =>
					   {
						   var t = GetChunk(content, ",", i, "Identifier");
						   bib.Id = t.Item2.Trim(new[] { '\t', ' ' });
						   s.UpdateObject(t.Item1 + 1);
						   return StateMachine.NamedState("PropDef");
					   })
				.State("PropDef",
					   (s, i) =>
					   	{
						   var t = GetChunk(content, "=", i, "Property Definition");
						   propName = t.Item2.Trim(new[] { '\t', ' ' });
						   s.UpdateObject(t.Item1 + 1);
						   return StateMachine.NamedState("PropValBracket");
					   })
				.State("PropValBracket",
					   (s, i) =>
					   {
						   if (content[i] != ' ' && content[i] != '{' && content[i] != '\t')
						   {
							   propValBuilder = new StringBuilder();
							   return StateMachine.NamedState("PropVal");
						   }
						   if (content[i] == '{')
						   {
							   // ReSharper disable AccessToModifiedClosure
							   bracketDepth++;
							   // ReSharper restore AccessToModifiedClosure
						   }
						   s.UpdateObject(i + 1);
						   return StateMachine.Loop;
					   })
				.State("PropVal",
					   (s, i) =>
					   {
						   s.UpdateObject(i + 1);
						   if (( i >= content.Length ) || ( bracketDepth <= 0 && content[i] != ' ' && content[i] != '}' && content[i] != '\t' && content[i] != ',' ))
							   throw new ArgumentOutOfRangeException(string.Format(
																				   "Error at position {0}, brackets not closed properly.",
																				   i));
						   if (content[i] == '{')
						   {
							   bracketDepth++;
						   }
						   if (content[i] == '}')
						   {
							   bracketDepth--;
						   }
						   if (bracketDepth == 0)
						   {
							   bib.Properties.Add(propName, propValBuilder.ToString().Trim('}'));
						   	return StateMachine.NamedState("CloseBracket");
						   }
					   	propValBuilder.Append(content[i]);
						   return StateMachine.Loop;
					   })
				.State("CloseBracket",
					(s,i) =>
						{
							s.UpdateObject(i + 1);
							if (( i >= content.Length ) || ( bracketDepth <= 0 && content[i] != ' ' && content[i] != '}' && content[i] != '\t' && content[i] != ',' ))
								throw new ArgumentOutOfRangeException(string.Format(
																					"Error at position {0}, brackets not closed properly.",
																					i));
							if (content[i] == ',')
								return StateMachine.NamedState("NewProperty");
							if (content[i] == '}')
							{
								returnI = i;
								return StateMachine.Break;
							}
							return StateMachine.Loop;
						})
				.State("NewProperty",
					(s,i) => 
					{
						if ( i >= content.Length )
							return StateMachine.Break;
						if (content[i] == '}')
						{
							returnI = i;
							return StateMachine.Break;
						}
						if (content[i] != ' ' && content[i] != '\t' && content[i] != ',')
							return StateMachine.NamedState("PropDef");
						s.UpdateObject(i + 1);
						return StateMachine.Loop;
					})
					.BindTo(initialI);
			while (!sm.Process())
			{ }
			return returnI;
		}

		private static Tuple<int, string> GetChunk(string s, string lookup, int i, string chunkDefinition)
		{
			var nextI = s.IndexOf(lookup, i);
			if (nextI < 0)
				throw new ArgumentOutOfRangeException(
								   string.Format("Incorrect format at position {0}, '{1}' expected.", i, chunkDefinition));
			var chunk = s.Substring(i, nextI - i);
			return Tuple.Create(nextI, chunk);
		}
	}
}